#!usr/bin/env python
# -*- coding:utf-8 _*-
"""
@author:zhengxin
@file: 01_pandas_data_structure.py
@time: 2022/10/12  14:14
# @describe: pandas的数据结构介绍

1. pandas: 它含有使数据清洗和分析工作变得更快更简单的数据结构和操作工具。pandas经常和其它工具一同使用，
    如数值计算工具NumPy和SciPy，分析库statsmodels和scikit-learn，和数据可视化库matplotlib。
    pandas是基于NumPy数组构建的，特别是基于数组的函数和不使用for循环的数据处理。

    虽然pandas采用了大量的NumPy编码风格，但二者最大的不同是pandas是专门为处理表格和混杂数据设计的。
    而NumPy更适合处理统一的数值数组数据。

2. 要使用pandas，你首先就得熟悉它的两个主要数据结构：Series和DataFrame。
    虽然它们并不能解决所有问题，但它们为大多数应用提供了一种可靠的、易于使用的基础。
"""

# Series是一种类似于一维数组的对象，它由一组数据（各种NumPy数据类型）以及一组与之相关的数据标签（即索引）组成。
# 仅由一组数据即可产生最简单的Series：
import numpy as np
import pandas as pd
from pandas import Series, DataFrame


obj = pd.Series([4, 7, -5, 3])
print(obj)

# Series的字符串表现形式为：索引在左边，值在右边。由于我们没有为数据指定索引，于是会自动创建一个0到N-1（N为数据的长度）的整数型索引。
# 你可以通过Series 的values和index属性获取其数组表示形式和索引对象：
print(obj.values)
print(obj.index)

# 通常，我们希望所创建的Series带有一个可以对各个数据点进行标记的索引：
obj2 = pd.Series([4, 7, -5, 3], index=['d', 'b', 'a', 'c'])
print(obj2)
print(obj2.index)

# 与普通NumPy数组相比，你可以通过索引的方式选取Series中的单个或一组值：
print(obj2['a'])
obj2['d'] = 6
print(obj2[['c', 'a', 'd']])

# 使用NumPy函数或类似NumPy的运算（如根据布尔型数组进行过滤、标量乘法、应用数学函数等）都会保留索引值的链接：
print(obj2[obj2 > 0])
print(obj2 * 2)
print(np.exp(obj2))

# 还可以将Series看成是一个定长的有序字典，因为它是索引值到数据值的一个映射。它可以用在许多原本需要字典参数的函数中：
print('b' in obj2)
print('e' in obj2)

# 如果数据被存放在一个Python字典中，也可以直接通过这个字典来创建Series：
sdata = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000}
obj3 = pd.Series(sdata)
print(obj3)

# 如果只传入一个字典，则结果Series中的索引就是原字典的键（有序排列）。你可以传入排好序的字典的键以改变顺序：
# sdata中跟states索引相匹配的那3个值会被找出来并放到相应的位置上，但由于”California”所对应的sdata值找不到,
# 所以其结果就为NaN（即“非数字”（not a number
states = ['California', 'Ohio', 'Oregon', 'Texas']
obj4 = pd.Series(sdata, index=states)
print(obj4)

# 使用缺失（missing）或NA表示缺失数据。pandas的isnull和notnull函数可用于检测缺失数据：
print(pd.isnull(obj4))
print(pd.notnull(obj4))

# Series也有类似的实例方法：
print(obj4.isnull())



""" 对于许多应用而言，Series最重要的一个功能是，它会根据运算的索引标签自动对齐数据： """
print(obj3)
print(obj4)
print(obj3 + obj4)

# Series对象本身及其索引都有一个name属性，该属性跟pandas其他的关键功能关系非常密切：
obj4.name = 'population'
obj4.index.name = 'state'
print(obj4)

# Series的索引可以通过赋值的方式就地修改：
print(obj)
obj.index = ['Bob', 'Steve', 'Jeff', 'Ryan']
print(obj)





""" 
DataFiame：
     DataFrame是一个表格型的数据结构，它含有一组有序的列，每列可以是不同的值类型（数值、字符串、布尔值等）。
     DataFrame既有行索引也有列索引，它可以被看做由Series组成的字典（共用同一个索引）。DataFrame中的数据是以
     一个或多个二维块存放的（而不是列表、字典或别的一维数据结构）。
 
 """
# 建DataFrame的办法有很多，最常用的一种是直接传入一个由等长列表或NumPy数组组成的字典：
data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'],
        'year': [2000, 2001, 2002, 2001, 2002, 2003],
        'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}
frame = pd.DataFrame(data)
# 结果DataFrame会自动加上索引（跟Series一样），且全部列会被有序排列：
# 如果你使用的是Jupyter notebook，pandas DataFrame对象会以对浏览器友好的HTML表格的方式呈现。
print(frame)

# 对于特别大的DataFrame，head方法会选取前五行：
print(frame.head())

# 如果指定了列序列，则DataFrame的列就会按照指定顺序进行排列：
print(pd.DataFrame(data, columns=['year', 'state', 'pop']))

# 如果传入的列在数据中找不到，就会在结果中产生缺失值：
frame2 = pd.DataFrame(data, columns=['year', 'state', 'pop', 'debt'],
                      index=['one', 'two', 'three', 'four',
                             'five', 'six'])
print(frame2)


# 通过类似字典标记的方式或属性的方式，可以将DataFrame的列获取为一个Series：
print(frame2['state'])
print(frame2.year)

# 行也可以通过位置或名称的方式进行获取，比如用loc属性：
print(frame2.loc['three'])


# 列可以通过赋值的方式进行修改。例如，我们可以给那个空的”debt”列赋上一个标量值或一组值：
frame2['debt'] = 16.5
print(frame2)

frame2['debt'] = np.arange(6.)
print(frame2)

# 将列表或数组赋值给某个列时，其长度必须跟DataFrame的长度相匹配。如果赋值的是一个Series，
# 就会精确匹配DataFrame的索引，所有的空位都将被填上缺失值：
val = pd.Series([-1.2, -1.5, -1.7], index=['two', 'four', 'five'])
frame2['debt'] = val
print(frame2)



# 为不存在的列赋值会创建出一个新列。关键字del用于删除列。
# 作为del的例子，我先添加一个新的布尔值的列，state是否为’Ohio’：
frame2['eastern'] = frame2.state == 'Ohio'
print("*" * 10)
print(frame2)

# del方法可以用来删除这列：
del frame2['eastern']
print(frame2.columns)



# 另一种常见的数据形式是嵌套字典：
pop = {'Nevada': {2001: 2.4, 2002: 2.9}, 'Ohio': {2000: 1.5, 2001: 1.7, 2002: 3.6}}

# 如果嵌套字典传给DataFrame，pandas就会被解释为：外层字典的键作为列，内层键则作为行索引：
frame3 = pd.DataFrame(pop)
print(frame3)

# 你也可以使用类似NumPy数组的方法，对DataFrame进行转置（交换行和列）：
print(frame3.T)

# 内层字典的键会被合并、排序以形成最终的索引。如果明确指定了索引，则不会这样：
print(pd.DataFrame(pop, index=[2001, 2002, 2003]))

# 如果设置了DataFrame的index和columns的name属性，则这些信息也会被显示出来：
frame3.index.name = 'year'
frame3.columns.name = 'state'
print(frame3)


# 跟Series一样，values属性也会以二维ndarray的形式返回DataFrame中的数据：
print(frame3.values)

# 如果DataFrame各列的数据类型不同，则值数组的dtype就会选用能兼容所有列的数据类型：
print(frame2.values)



""" 索引对象 """
# pandas的索引对象负责管理轴标签和其他元数据（比如轴名称等）。
# 构建Series或DataFrame时，所用到的任何数组或其他序列的标签都会被转换成一个Index：
obj = pd.Series(range(3), index=['a', 'b', 'c'])
index = obj.index
print('*' * 10)
print(index)
print(index[1:])


# Index对象是不可变的，因此用户不能对其进行修改：
# index[1] = 'd'

# 不可变可以使Index对象在多个数据结构之间安全共享：
labels = pd.Index(np.arange(3))
print('*' * 10)
print(labels)

obj2 = pd.Series([1.5, -2.5, 0], index=labels)
print(obj2)
print(obj2.index is labels)


# 除了类似于数组，Index的功能也类似一个固定大小的集合：
print('*' * 10)
print(frame3)
print(frame3.columns)
print('Ohio' in frame3.columns)
print(2003 in frame3.index)

# 与python的集合不同，pandas的Index可以包含重复的标签：
dup_labels = pd.Index(['foo', 'foo', 'bar', 'bar'])
print(dup_labels)

